package cn.itsource.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.itsource.admin.dto.RuleDto;
import cn.itsource.api.RuleFeignClient;
import cn.itsource.constants.*;
import cn.itsource.domain.dto.DriverSettingRedisDto;
import cn.itsource.exception.GlobalException;
import cn.itsource.mapper.OrderBillMapper;
import cn.itsource.pojo.domain.Login;
import cn.itsource.pojo.domain.Order;
import cn.itsource.mapper.OrderMapper;
import cn.itsource.pojo.domain.OrderBill;
import cn.itsource.pojo.dto.*;
import cn.itsource.pojo.vo.OrderInfoVo;
import cn.itsource.pojo.vo.Position;
import cn.itsource.pojo.vo.RunningOrderInfoVo;
import cn.itsource.result.JSONResult;
import cn.itsource.service.IOrderService;
import cn.itsource.utils.CodeGenerateUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer;
import org.apache.rocketmq.client.consumer.PullResult;
import org.apache.rocketmq.client.consumer.PullStatus;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.message.MessageQueue;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.*;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import static org.springframework.messaging.support.MessageBuilder.withPayload;

/**
 * <p>
 * 订单表 服务实现类
 * </p>
 *
 * @author lifan
 * @since 2024-09-20
 */
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {

    @Autowired
    private OrderBillMapper orderBillMapper;
    @Autowired
    private RuleFeignClient ruleFeignClient;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RocketMQTemplate rocketMQTemplate;
    @Autowired
    private DefaultMQPullConsumer pullConsumer;

    @Autowired
    private RedissonClient redissonClient;


    /**
     * 司机抢单
     * @param orderNo
     * @return
     */
    @Override
    public JSONResult grabOrder(String orderNo) {
        RLock lock = redissonClient.getLock(Constants.ACCEPT_ORDERS_LOCK);
        try {
            lock.lock(); // 开锁
            // 查询该订单是否还在
            Order one = super.getOne(new LambdaQueryWrapper<Order>()
                    .eq(Order::getOrderNo, orderNo)
                    .eq(Order::getStatus, Constants.orderStatus.ORDER_STATUS_WAIT));
            if (one != null){
                driverUpdateOrder(one);//订单在，修改订单
                OrderInfoVo orderInfoVo = new OrderInfoVo();
                BeanUtils.copyProperties(one, orderInfoVo);
                orderInfoVo.setFavourFee(one.getFavourAmount());
                return JSONResult.success(orderInfoVo);
            }
            return JSONResult.success(ErrorCode.GRAB_ORDERS_FAIL);
        } finally {
            lock.unlock(); // 关锁
        }
    }

    /**
     * 检查是否存在订单
     * @return
     */
    @Override
    public JSONResult checkRunningOrder() {
        // 查询运行的订单
        Order one = super.getOne(new LambdaQueryWrapper<Order>()
                .eq(Order::getCustomerId, StpUtil.getLoginIdAsLong())
                .in(Order::getStatus, Constants.orderStatus.getCheckRunningOrder()));
        //起始位置
        Position from = new Position();
        from.setAddress(one.getStartPlace());
        from.setLongitude(one.getStartPlaceLongitude());
        from.setLatitude(one.getStartPlaceLatitude());
        //结束位置
        Position to = new Position();
        to.setAddress(one.getEndPlace());
        to.setLongitude(one.getEndPlaceLongitude());
        to.setLatitude(one.getEndPlaceLatitude());
        return JSONResult.success(new RunningOrderInfoVo(from,to));
    }

    @Override
    public JSONResult receivedAnOrder(String orderNO) {
        Order one = this.getOne(new LambdaQueryWrapper<Order>()
                .eq(Order::getOrderNo, orderNO)
                .eq(Order::getStatus, Constants.orderStatus.ORDER_STATUS_WAIT));
        if (one == null){
            return JSONResult.success(true);
        }
        return JSONResult.success(false);
    }

    @Override
    public JSONResult driverRunningOrder() {
        Order one = super.getOne(new LambdaQueryWrapper<Order>()
                .eq(Order::getDriverId, StpUtil.getLoginIdAsLong())
                .in(Order::getStatus, Constants.orderStatus.getCheckRunningOrder()));
        OrderInfoVo orderInfoVo = new OrderInfoVo();
        BeanUtils.copyProperties(one,orderInfoVo);
        orderInfoVo.setFavourFee(one.getFavourAmount());
        return JSONResult.success(orderInfoVo);
    }

    /**
     * 到达代驾点
     * @param orderNo
     * @return
     */
    @Override
    public JSONResult arriveStartPlaceHandle(String orderNo) {
        Order one = super.getOne(new LambdaQueryWrapper<Order>()
                .eq(Order::getOrderNo, orderNo));
        Date date = new Date();
        one.setArriveTime(date); //到达时间
        one.setUpdateTime(date);
        one.setStatus(Constants.orderStatus.ORDER_STATUS_ARRIVE);
        super.updateById(one);
        return JSONResult.success();
    }

    /**
     * 开始代驾
     * @param orderNo
     * @return
     */
    @Override
    public JSONResult startDrivingHandle(String orderNo) {
        Order one = super.getOne(new LambdaQueryWrapper<Order>()
                .eq(Order::getOrderNo, orderNo));
        Date date = new Date();
        one.setUpdateTime(date);
        one.setStartTime(date); //代驾开始时间
        one.setStatus(Constants.orderStatus.ORDER_STATUS_START_DRIVING);
        super.updateById(one);
        return JSONResult.success();
    }

    /**
     * 结束代驾
     * @param orderNo
     * @return
     */
    @Override
    public JSONResult endDrivingHandle(String orderNo) {
        Order one = super.getOne(new LambdaQueryWrapper<Order>()
                .eq(Order::getOrderNo, orderNo));
        Date date = new Date();
        one.setUpdateTime(date);
        one.setEndTime(date); //代驾结束时间
        one.setStatus(Constants.orderStatus.ORDER_STATUS_COMPLETE_DRIVED);
        super.updateById(one);
        return JSONResult.success();
    }

    /**
     * 计算订单费用
     * @param confirmFeesDto
     * @return
     */
    @Override
    public JSONResult confirmFees(ConfirmFeesDto confirmFeesDto) {
        //查询结束的订单
        Order one = super.getOne(new LambdaQueryWrapper<Order>()
                .eq(Order::getOrderNo, confirmFeesDto.getOrderNo())
                .eq(Order::getStatus, Constants.orderStatus.ORDER_STATUS_COMPLETE_DRIVED));
        if(one == null){
            return JSONResult.error("未查询到订单数据");
        }
        //结束订单的修改
        updateOrder(one);
        //修改账单数据
        OrderBill orderBill = updateOrderBill(confirmFeesDto, one);
        return JSONResult.success();
    }
    private void updateOrder(Order one) {
        //实际里程  -- 从hbase里面去取
        //1.调用hbase根据orderNo去获取坐标点系
        //2.使用腾讯地图工具类 将坐标系传入 得到 坐标系距离
        //todo
        one.setRealMileage(one.getExpectsMileage());
        //返程里程
        one.setReturnMileage(one.getRealMileage());
        //实际订单价格
        JSONResult valuation = ruleFeignClient.valuation(one.getExpectsMileage().toString());
        if(!valuation.isSuccess()){
            throw new GlobalException(ErrorCode.SERVICE_ERROR);
        }
        one.setRealOrderAmount(new BigDecimal(valuation.getData().toString()));
        //修改订单状态
        one.setStatus(Constants.orderStatus.ORDER_STATUS_ENSURE); //司机确定费用
        one.setUpdateTime(new Date());
        super.updateById(one);
    }
    private OrderBill updateOrderBill(ConfirmFeesDto confirmFeesDto, Order one) {
        //根据订单id查询账单
        OrderBill orderBill = orderBillMapper.selectOne(new LambdaQueryWrapper<OrderBill>()
                .eq(OrderBill::getOrderId, one.getId()));
        orderBill.setRealOrderAmount(one.getRealOrderAmount());
        BigDecimal bigDecimal = new BigDecimal(confirmFeesDto.getTollFee());
        orderBill.setTollAmount(bigDecimal);
        orderBill.setFavourAmount(one.getFavourAmount());
        //exceed_base_mileage = 实际里程 - 基础里程
        if(one.getRealMileage().compareTo(orderBill.getBaseMileage()) > RConst.Num.INT_0){ // 超出基础公里数额外算钱
            orderBill.setExceedBaseMileage(one.getRealMileage().subtract(orderBill.getBaseMileage())); //
            //exceed_base_mileage_amount = 超出公里数 * 超出公里价格
            orderBill.setExceedBaseMileageAmount(orderBill.getExceedBaseMileage()
                    .multiply(orderBill.getExceedBaseMileageEveryKmAmount()));
        }
        //里程费(基础+超出)
        orderBill.setMileageAmount(orderBill.getBaseMileageAmount().add(orderBill.getExceedBaseMileageAmount()));
        //等待时长 = 开始代驾 时间 - 到达时间
        Long waitTime = (one.getStartTime().getTime() - one.getArriveTime().getTime())
                / (RConst.Num.INT_1000*RConst.Num.LONG_60);
        orderBill.setWaitingMinute(waitTime.intValue());//单位分钟
        if(orderBill.getFreeBaseWaitingMinute()<waitTime){
            //计算超出分钟数
            int overWaitTime = waitTime.intValue()-orderBill.getFreeBaseWaitingMinute();
            orderBill.setExeceedBaseWaitingMinute(
                    new BigDecimal(overWaitTime));
            //计算超出分钟数价格
            BigDecimal multiply = orderBill.getExeceedBaseWaitingEveryKmAmount().multiply(new BigDecimal(overWaitTime));
            orderBill.setWaitingAmount(multiply);
        }
        //返回里程数
        orderBill.setReturnMileage(one.getRealMileage());
        if(orderBill.getReturnMileage().compareTo(orderBill.getFreeBaseReturnMileage())>RConst.Num.INT_0){
            //计算超出返回里程数
            orderBill.setExceedFreeBaseReturnMileage(orderBill.getReturnMileage()
                    .subtract(orderBill.getFreeBaseReturnMileage()));
            //计算返程超出费用
            orderBill.setReturnAmont(orderBill.getExceedFreeBaseReturnMileage()
                    .multiply(orderBill.getExceedBaseReturnEveryKmAmount()));
        }
        orderBill.setParkingAmount(new BigDecimal(confirmFeesDto.getParkingFee()));
        orderBill.setUpdateTime(new Date());
        orderBillMapper.updateById(orderBill);
        return orderBill;
    }


    private void driverUpdateOrder(Order one) {
        one.setStatus(Constants.orderStatus.ORDER_STATUS_ACCEPTED);
        Date date = new Date();
        one.setAcceptTime(date);
        one.setUpdateTime(date);
        // 获取登录司机信息
        Object extra = StpUtil.getExtra(Constants.CURRENT_LOGIN_KEY);
        Login login = JSONObject.parseObject(extra.toString(), Login.class);
        one.setDriverId(login.getId());
        one.setDriverName(login.getUsername());
        one.setDriverPhone(login.getPhone());
        one.setDriverPhoto(login.getAvatar());
        super.updateById(one);
    }

    /**
     * 司机拉取订单消息
     * @return
     */
    @Override
    public JSONResult pullCustomerOrder() {
        //要从MQ里面主动获取消息
        // 知道 TOPIC TAG driverId
        // 从MQ里面去取值  用的是pull
        ArrayList<PushDriverOrderMsgDto> list = new ArrayList<>(); // 创建一个集合装订单信息
        long driverId = StpUtil.getLoginIdAsLong();
        // 存数据时的消息路径
        // RocketMQConstants.PUSH_DRIVER_ORDER_TOPIC+":"+String.format(RocketMQConstants.PUSH_DRIVER_ORDER_TAG,driverId);
        try {
            // 获取 对应地topic队列
            Set<MessageQueue> messageQueues =
                    pullConsumer.fetchSubscribeMessageQueues(RocketMQConstants.PUSH_DRIVER_ORDER_TOPIC);
            //默认四个队列 循环队列从中找到数据
            for (MessageQueue messageQueue : messageQueues) {
                //  偏移量
                long l = pullConsumer.fetchConsumeOffset(messageQueue, true);
                String tag = String.format(RocketMQConstants.PUSH_DRIVER_ORDER_TAG, driverId);
                // 从mq中拉取 消息
                PullResult pull = pullConsumer.pull(messageQueue, tag, l, 32);
                if (pull != null && pull.getPullStatus().equals(PullStatus.FOUND)){
                    // 拿到消息列表
                    List<MessageExt> msgFoundList = pull.getMsgFoundList();
                    if (msgFoundList.size() > 0){
                        for (MessageExt messageExt : msgFoundList) {
                            // 将消息类容转换为 UTF-8 编码的字符串
                            String s = new String(messageExt.getBody(), StandardCharsets.UTF_8);
                            // 使用json解析字符串为指定对象
                            PushDriverOrderMsgDto pushDriverOrderMsgDto = JSONObject.parseObject(s, PushDriverOrderMsgDto.class);
                            if (pushDriverOrderMsgDto.getFavourAmount() == null){
                                pushDriverOrderMsgDto.setFavourAmount(BigDecimal.ZERO);
                            }
                            list.add(pushDriverOrderMsgDto);
                        }
                    }else {
                        continue;
                    }

                }
                if(pull != null){
                    //重要：修改消息的消费位置，如果位置不后移，消息会一直被消费
                    pullConsumer.updateConsumeOffset(messageQueue,pull.getNextBeginOffset());
                }
            }
            return JSONResult.success(list);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public JSONResult cancelOrder(CancelOrderDto cancelOrderDto) {
        if (cancelOrderDto.getType() == RConst.Num.INT_1){ // 司机取消
            Order one = super.getOne(new LambdaQueryWrapper<Order>()
                    .eq(Order::getOrderNo, cancelOrderDto.getOrderNo()));
            if (one == null) return JSONResult.success();
            // 司机取消订单，应该会有一个罚款的 todo
            one.setStatus(Constants.orderStatus.ORDER_STATUS_DRIVER_CANCEL_ORDER);
            one.setUpdateTime(new Date());
            super.updateById(one);
        }else if (cancelOrderDto.getType()== RConst.Num.INT_2){ // 乘客取消
            Order one = super.getOne(new LambdaQueryWrapper<Order>()
                    .eq(Order::getOrderNo, cancelOrderDto.getOrderNo()));
            if (one == null) return JSONResult.success();
            // 订单没有被司机接单的状态
            if (one.getStatus().equals(Constants.orderStatus.ORDER_STATUS_WAIT)){
                // 修改订单状态
                one.setStatus(Constants.orderStatus.ORDER_STATUS_CUSTOMER_CANCEL_ORDER);
                one.setUpdateTime(new Date());
                super.updateById(one);
            } else if (one.getStatus().equals(Constants.orderStatus.ORDER_STATUS_ACCEPTED)) { //订单被接了，就需扣除手续费 todo
                // 修改订单状态
                one.setStatus(Constants.orderStatus.ORDER_STATUS_CUSTOMER_CANCEL_ORDER);
                one.setUpdateTime(new Date());
                super.updateById(one);
            }
        }
        return JSONResult.success();

    }


    /**
     * 乘客下单的初始化数据等
     * @param orderDto
     * @return
     */
    @Override
    public JSONResult save(OrderDto orderDto) {
        long loginId = StpUtil.getLoginIdAsLong();
        //根据登录人生成唯一的订单号 工具类CodeGenerateUtils
        String orderNo = CodeGenerateUtils.generateOrderSn(loginId);
        // 保存订单
        Order order = saveOrder(orderDto, orderNo, loginId);
        //初始化账单
        initializeBill(order);
        // 寻找附近的司机id
        List<GeoDriverDto> driverIds = findDrivers(orderDto);
        // 根据id找符合司机的配置
        List<GeoDriverDto> appropriateDrivers = getDriverIdSetting(driverIds, order);
        // 向符合的司机推送订单
        pushOrderMessage(appropriateDrivers,order);
        return JSONResult.success(orderNo);
    }

    /**
     * 向符合的司机推送订单信息
     * @param appropriateDrivers
     * @param order
     */
    private void pushOrderMessage(List<GeoDriverDto> appropriateDrivers, Order order) {
        for (GeoDriverDto driver : appropriateDrivers) {
            // 创建要发送的信息
            PushDriverOrderMsgDto pushDriverOrderMsgDto = new PushDriverOrderMsgDto();
            BeanUtils.copyProperties(order, pushDriverOrderMsgDto); // 从订单中获取
            pushDriverOrderMsgDto.setToDriverMileage(driver.getValue()); //距离司机的距离
            // 创建要发送的消息对象
            Message<PushDriverOrderMsgDto> message = withPayload(pushDriverOrderMsgDto).build();
            //把信息对象发送到RocketMQ中
            rocketMQTemplate.syncSend(RocketMQConstants.PUSH_DRIVER_ORDER_TOPIC+":" +
                    String.format(RocketMQConstants.PUSH_DRIVER_ORDER_TAG,driver.getDriverId()),
                    message);
        }
    }

    /**
     * 查询符合条件的司机
     * @param driverIds
     * @param order
     * @return
     */
    private List<GeoDriverDto> getDriverIdSetting(List<GeoDriverDto> driverIds, Order order) {
        List<GeoDriverDto> appropriateDrivers = new ArrayList<>();
        for (GeoDriverDto driverId : driverIds) {
            Boolean b = redisTemplate.hasKey(CacheConstants.DRIVER_POINT_SETTING + driverId.getDriverId());
            if (!b){ // 如果redis中没有该司机，就代表该司机没在线 ，就不退
                continue;
            }
            // 获取司机配置
            DriverSettingRedisDto driverSettingRedisDto = (DriverSettingRedisDto) redisTemplate.opsForValue()
                    .get(CacheConstants.DRIVER_POINT_SETTING + driverId.getDriverId());
            //获取司机配置的做大公里数
            Integer orderDistance = driverSettingRedisDto.getOrderDistance();
            if (orderDistance < order.getExpectsMileage().intValue()){ // 如果乘客的做大公里数大于的司机配置中的最大公里数，就不向该司机推送
                continue;
            }
            if (!driverSettingRedisDto.getListenService()){ //判断该司机是否开启自动听单
                continue;
            }
            // 接单条件：订单距离(公里) 订单的距离大于的司机配置的距离不退
            if(driverId.getValue() > driverSettingRedisDto.getRangeDistance()){
                continue;
            }
            // 符合司机添加集合
            appropriateDrivers.add(driverId);
        }
        return appropriateDrivers;
    }

    /**
     * 获取乘客附近的司机id
     * @param orderDto
     * @return
     */
    private List<GeoDriverDto> findDrivers(OrderDto orderDto) {
        // 等到乘客的开始位置 point坐标对象
        Point point = new Point(Double.valueOf(orderDto.getEndPlaceLongitude()),
                Double.valueOf(orderDto.getStartPlaceLatitude()));
        // 设置半径 坐标，半径
        Circle circle = new Circle(point,new Distance(RConst.Num.BASE_LONG_2, Metrics.KILOMETERS));
        /**
         * 构建查询参数
         * 创建了一个新的地理半径查询参数对象
         * 这个对象可以用于配置和定制对 Redis 中存储的地理空间数据进行半径查询的各种选项，
         * 比如可以设置查询的单位、排序方式、返回结果的数量限制等
         */

        RedisGeoCommands.GeoRadiusCommandArgs geoRadiusCommandArgs =
                RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs();

        geoRadiusCommandArgs.includeDistance()//结果中包含 距离
                .includeCoordinates()//结果中包含坐标
                .sortAscending() //由近到远
                .limit(RConst.Num.BASE_LONG_5); //匹配五个

        // 查询这个点（乘客开始位置）范围的其他点（司机位置）
        GeoResults<RedisGeoCommands.GeoLocation<Object>> radius =
                redisTemplate.opsForGeo().radius(CacheConstants.DRIVER_POINT, circle, geoRadiusCommandArgs);
        //得到查询位置的集合
        List<GeoResult<RedisGeoCommands.GeoLocation<Object>>> driverList = radius.getContent();
        List<GeoDriverDto> driverIds = driverList.stream()
                .map(i ->
                        new GeoDriverDto(Long.valueOf(i.getContent().getName().toString()), // 获取司机的id转为long型
                                i.getDistance().getValue(), //获取距离值
                                i.getDistance().getUnit()) //获取距离单位
                ).collect(Collectors.toList());
        return driverIds;
    }

    /**
     * 初始化账单
     * @param order
     */
    private void initializeBill(Order order) {
        OrderBill orderBill = new OrderBill();
        orderBill.setOrderId(order.getId());
        orderBill.setOrderNo(order.getOrderNo());
        //获取初始化基础数据，如预计金额这些
        JSONResult ruleDto = ruleFeignClient.getRuleDto(LocalDateTime.now().getHour());
//        BeanUtils.copyProperties(ruleDto, orderBill); // 开始对象，目标对象
        Object data = ruleDto.getData();
        RuleDto ruleDto1 = JSONObject.parseObject(JSON.toJSONString(data), RuleDto.class);
        BeanUtils.copyProperties(ruleDto1,orderBill);
        orderBillMapper.insert(orderBill);
    }

    /**
     * 保存订单
     * @param orderDto
     * @param orderNo
     * @param loginId
     * @return
     */
    private Order   saveOrder(OrderDto orderDto, String orderNo, long loginId) {
        // 请求数据orderDtoToOrder方法直接将orderDto中的数据赋值给order中，前提是属性类型和属性名一致
        Order order = OrderDto.orderDtoToOrder(orderDto);
        order.setOrderNo(orderNo);
        order.setCustomerId(loginId);
        order.setCreateTime(new Date());
        order.setStatus(Constants.orderStatus.ORDER_STATUS_WAIT); //等待接单
        // 从stpUtil取出来的登录时的扩展参数，类型时jsonObject
        Object extra = StpUtil.getExtra(Constants.CURRENT_LOGIN_KEY);
        Login login = JSONObject.parseObject(extra.toString(), Login.class); //获取登录的乘客信息
        order.setCustomerName(login.getUsername());
        order.setCustomerPhone(login.getPhone());
        order.setCustomerPhoto(login.getAvatar());
        // 获取计算预计总费用
        JSONResult totalAmount = ruleFeignClient.valuation(orderDto.getExpectsMileage().toString());
        if (!totalAmount.isSuccess()) {
            throw new GlobalException(ErrorCode.SERVICE_ERROR);
        }
        order.setExpectsOrderAmount(new BigDecimal(totalAmount.getData().toString()));
        //mybatisPlus执行添加后会自动返回id到对象里面
        super.save(order);
        return order;
    }
}
